<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="popupBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="popup-base">
    <resources>
      <stylesheet src="chrome://global/skin/popup.css"/>
    </resources>

    <implementation implements="nsIDOMXULPopupElement">
      <property name="label" onget="return this.getAttribute('label');"
                             onset="this.setAttribute('label', val); return val;"/>
      <property name="position" onget="return this.getAttribute('position');"
                                onset="this.setAttribute('position', val); return val;"/>
      <property name="popupBoxObject">
        <getter>
          return this.boxObject;
        </getter>
      </property>

#ifdef MOZ_WIDGET_GTK
      <property name="state" readonly="true">
        <getter>
        <![CDATA[
          if (this.hasAttribute('_moz-nativemenupopupstate'))
            return this.getAttribute('_moz-nativemenupopupstate');
          else
            return this.popupBoxObject.popupState;
        ]]>
        </getter>
      </property>
#else
      <property name="state" readonly="true"
                onget="return this.popupBoxObject.popupState"/>
#endif

      <property name="triggerNode" readonly="true"
                onget="return this.popupBoxObject.triggerNode"/>

      <property name="anchorNode" readonly="true"
                onget="return this.popupBoxObject.anchorNode"/>

      <method name="openPopup">
        <parameter name="aAnchorElement"/>
        <parameter name="aPosition"/>
        <parameter name="aX"/>
        <parameter name="aY"/>
        <parameter name="aIsContextMenu"/>
        <parameter name="aAttributesOverride"/>
        <parameter name="aTriggerEvent"/>
        <body>
        <![CDATA[
          try {
            var popupBox = this.popupBoxObject;
            if (popupBox)
              popupBox.openPopup(aAnchorElement, aPosition, aX, aY,
                                 aIsContextMenu, aAttributesOverride, aTriggerEvent);
          } catch (e) {}
        ]]>
        </body>
      </method>

      <method name="openPopupAtScreen">
        <parameter name="aX"/>
        <parameter name="aY"/>
        <parameter name="aIsContextMenu"/>
        <parameter name="aTriggerEvent"/>
        <body>
        <![CDATA[
          try {
            var popupBox = this.popupBoxObject;
            if (popupBox)
              popupBox.openPopupAtScreen(aX, aY, aIsContextMenu, aTriggerEvent);
          } catch (e) {}
        ]]>
        </body>
      </method>

      <method name="openPopupAtScreenRect">
        <parameter name="aPosition"/>
        <parameter name="aX"/>
        <parameter name="aY"/>
        <parameter name="aWidth"/>
        <parameter name="aHeight"/>
        <parameter name="aIsContextMenu"/>
        <parameter name="aAttributesOverride"/>
        <parameter name="aTriggerEvent"/>
        <body>
        <![CDATA[
          try {
            var popupBox = this.popupBoxObject;
            if (popupBox)
              popupBox.openPopupAtScreenRect(aPosition, aX, aY, aWidth, aHeight,
                                             aIsContextMenu, aAttributesOverride, aTriggerEvent);
          } catch (e) {}
        ]]>
        </body>
      </method>

      <method name="showPopup">
        <parameter name="element"/>
        <parameter name="xpos"/>
        <parameter name="ypos"/>
        <parameter name="popuptype"/>
        <parameter name="anchoralignment"/>
        <parameter name="popupalignment"/>
        <body>
        <![CDATA[
          var popupBox = null;
          var menuBox = null;
          try {
            popupBox = this.popupBoxObject;
          } catch (e) {}
          try {
            menuBox = this.parentNode.boxObject;
          } catch (e) {}
          if (menuBox instanceof MenuBoxObject)
            menuBox.openMenu(true);
          else if (popupBox)
            popupBox.showPopup(element, this, xpos, ypos, popuptype, anchoralignment, popupalignment);
        ]]>
        </body>
      </method>

      <method name="hidePopup">
        <parameter name="cancel"/>
        <body>
        <![CDATA[
          var popupBox = null;
          var menuBox = null;
          try {
            popupBox = this.popupBoxObject;
          } catch (e) {}
          try {
            menuBox = this.parentNode.boxObject;
          } catch (e) {}
          if (menuBox instanceof MenuBoxObject)
            menuBox.openMenu(false);
          else if (popupBox instanceof PopupBoxObject)
            popupBox.hidePopup(cancel);
        ]]>
        </body>
      </method>

      <property name="autoPosition">
        <getter>
        <![CDATA[
          return this.popupBoxObject.autoPosition;
        ]]>
        </getter>
        <setter>
        <![CDATA[
          return this.popupBoxObject.autoPosition = val;
        ]]>
        </setter>
      </property>

      <property name="alignmentPosition" readonly="true">
        <getter>
        <![CDATA[
          return this.popupBoxObject.alignmentPosition;
        ]]>
        </getter>
      </property>

      <property name="alignmentOffset" readonly="true">
        <getter>
        <![CDATA[
          return this.popupBoxObject.alignmentOffset;
        ]]>
        </getter>
      </property>

      <method name="enableKeyboardNavigator">
        <parameter name="aEnableKeyboardNavigator"/>
        <body>
        <![CDATA[
          this.popupBoxObject.enableKeyboardNavigator(aEnableKeyboardNavigator);
        ]]>
        </body>
      </method>

      <method name="enableRollup">
        <parameter name="aEnableRollup"/>
        <body>
        <![CDATA[
          this.popupBoxObject.enableRollup(aEnableRollup);
        ]]>
        </body>
      </method>

      <method name="sizeTo">
        <parameter name="aWidth"/>
        <parameter name="aHeight"/>
        <body>
        <![CDATA[
          this.popupBoxObject.sizeTo(aWidth, aHeight);
        ]]>
        </body>
      </method>

      <method name="moveTo">
        <parameter name="aLeft"/>
        <parameter name="aTop"/>
        <body>
        <![CDATA[
          this.popupBoxObject.moveTo(aLeft, aTop);
        ]]>
        </body>
      </method>

      <method name="moveToAnchor">
        <parameter name="aAnchorElement"/>
        <parameter name="aPosition"/>
        <parameter name="aX"/>
        <parameter name="aY"/>
        <parameter name="aAttributesOverride"/>
        <body>
        <![CDATA[
          this.popupBoxObject.moveToAnchor(aAnchorElement, aPosition, aX, aY, aAttributesOverride);
        ]]>
        </body>
      </method>

      <method name="getOuterScreenRect">
        <body>
        <![CDATA[
          return this.popupBoxObject.getOuterScreenRect();
        ]]>
        </body>
      </method>

      <method name="setConstraintRect">
        <parameter name="aRect"/>
        <body>
        <![CDATA[
          this.popupBoxObject.setConstraintRect(aRect);
        ]]>
        </body>
      </method>
    </implementation>

  </binding>

  <binding id="popup" role="xul:menupopup"
           extends="chrome://global/content/bindings/popup.xml#popup-base">

    <content>
      <xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical"
                          smoothscroll="false">
        <children/>
      </xul:arrowscrollbox>
    </content>

    <handlers>
      <handler event="popupshowing" phase="target">
        <![CDATA[
          var array = [];
          var width = 0;
          for (var menuitem = this.firstChild; menuitem; menuitem = menuitem.nextSibling) {
            if (menuitem.localName == "menuitem" && menuitem.hasAttribute("acceltext")) {
              var accel = document.getAnonymousElementByAttribute(menuitem, "anonid", "accel");
              if (accel && accel.boxObject) {
                array.push(accel);
                if (accel.boxObject.width > width)
                  width = accel.boxObject.width;
              }
            }
          }
          for (var i = 0; i < array.length; i++)
            array[i].width = width;
        ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="panel" role="xul:panel"
           extends="chrome://global/content/bindings/popup.xml#popup-base">
    <implementation implements="nsIDOMXULPopupElement">
      <field name="_prevFocus">0</field>
      <field name="_dragBindingAlive">true</field>
      <constructor>
      <![CDATA[
        if (this.getAttribute("backdrag") == "true" && !this._draggableStarted) {
          this._draggableStarted = true;
          try {
            let tmp = {};
            Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
            let draghandle = new tmp.WindowDraggingElement(this);
            draghandle.mouseDownCheck = function () {
              return this._dragBindingAlive;
            }
          } catch (e) {}
        }
      ]]>
      </constructor>
    </implementation>

    <handlers>
      <handler event="popupshowing"><![CDATA[
        // Capture the previous focus before has a chance to get set inside the panel
        try {
          this._prevFocus = Components.utils
                            .getWeakReference(document.commandDispatcher.focusedElement);
          if (this._prevFocus.get())
            return;
        } catch (ex) { }

        this._prevFocus = Components.utils.getWeakReference(document.activeElement);
      ]]></handler>
      <handler event="popupshown"><![CDATA[
        // Fire event for accessibility APIs
        var alertEvent = document.createEvent("Events");
        alertEvent.initEvent("AlertActive", true, true);
        this.dispatchEvent(alertEvent);
       ]]></handler>
      <handler event="popuphiding"><![CDATA[
        try {
          this._currentFocus = document.commandDispatcher.focusedElement;
        } catch (e) {
          this._currentFocus = document.activeElement;
        }
      ]]></handler>
      <handler event="popuphidden"><![CDATA[
        function doFocus() {
          // Focus was set on an element inside this panel,
          // so we need to move it back to where it was previously
          try {
            let fm = Components.classes["@mozilla.org/focus-manager;1"]
                               .getService(Components.interfaces.nsIFocusManager);
            fm.setFocus(prevFocus, fm.FLAG_NOSCROLL);
          } catch (e) {
            prevFocus.focus();
          }
        }
        var currentFocus = this._currentFocus;
        var prevFocus = this._prevFocus ? this._prevFocus.get() : null;
        this._currentFocus = null;
        this._prevFocus = null;

        // Avoid changing focus if focus changed while we hide the popup
        // (This can happen e.g. if the popup is hiding as a result of a
        // click/keypress that focused something)
        let nowFocus;
        try {
          nowFocus = document.commandDispatcher.focusedElement;
        } catch (e) {
          nowFocus = document.activeElement;
        }
        if (nowFocus && nowFocus != currentFocus)
          return;

        if (prevFocus && this.getAttribute("norestorefocus") != "true") {
          // Try to restore focus
          try {
            if (document.commandDispatcher.focusedWindow != window)
              return; // Focus has already been set to a window outside of this panel
          } catch (ex) {}

          if (!currentFocus) {
            doFocus();
            return;
          }
          while (currentFocus) {
            if (currentFocus == this) {
              doFocus();
              return;
            }
            currentFocus = currentFocus.parentNode;
          }
        }
      ]]></handler>
    </handlers>
  </binding>

  <binding id="arrowpanel" extends="chrome://global/content/bindings/popup.xml#panel">
    <content flip="both" side="top" position="bottomcenter topleft" consumeoutsideclicks="false">
      <xul:vbox anonid="container" class="panel-arrowcontainer" flex="1"
               xbl:inherits="side,panelopen">
        <xul:box anonid="arrowbox" class="panel-arrowbox">
          <xul:image anonid="arrow" class="panel-arrow" xbl:inherits="side"/>
        </xul:box>
        <xul:box class="panel-arrowcontent" xbl:inherits="side,align,dir,orient,pack" flex="1">
          <children/>
        </xul:box>
      </xul:vbox>
    </content>
    <implementation>
      <field name="_fadeTimer">null</field>
      <method name="sizeTo">
        <parameter name="aWidth"/>
        <parameter name="aHeight"/>
        <body>
        <![CDATA[
          this.popupBoxObject.sizeTo(aWidth, aHeight);
          if (this.state == "open") {
            this.adjustArrowPosition();
          }
        ]]>
        </body>
      </method>
      <method name="moveToAnchor">
        <parameter name="aAnchorElement"/>
        <parameter name="aPosition"/>
        <parameter name="aX"/>
        <parameter name="aY"/>
        <parameter name="aAttributesOverride"/>
        <body>
        <![CDATA[
          this.popupBoxObject.moveToAnchor(aAnchorElement, aPosition, aX, aY, aAttributesOverride);
        ]]>
        </body>
      </method>
      <method name="adjustArrowPosition">
        <body>
        <![CDATA[
        var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow");

        var anchor = this.anchorNode;
        if (!anchor) {
          return;
        }

        var container = document.getAnonymousElementByAttribute(this, "anonid", "container");
        var arrowbox = document.getAnonymousElementByAttribute(this, "anonid", "arrowbox");

        var position = this.alignmentPosition;
        var offset = this.alignmentOffset;

        this.setAttribute("arrowposition", position);

        if (position.indexOf("start_") == 0 || position.indexOf("end_") == 0) {
          container.orient = "horizontal";
          arrowbox.orient = "vertical";
          if (position.indexOf("_after") > 0) {
            arrowbox.pack = "end";
          } else {
            arrowbox.pack = "start";
          }
          arrowbox.style.transform = "translate(0, " + -offset + "px)";

          // The assigned side stays the same regardless of direction.
          var isRTL = (window.getComputedStyle(this).direction == "rtl");

          if (position.indexOf("start_") == 0) {
            container.dir = "reverse";
            this.setAttribute("side", isRTL ? "left" : "right");
          }
          else {
            container.dir = "";
            this.setAttribute("side", isRTL ? "right" : "left");
          }
        }
        else if (position.indexOf("before_") == 0 || position.indexOf("after_") == 0) {
          container.orient = "";
          arrowbox.orient = "";
          if (position.indexOf("_end") > 0) {
            arrowbox.pack = "end";
          } else {
            arrowbox.pack = "start";
          }
          arrowbox.style.transform = "translate(" + -offset + "px, 0)";

          if (position.indexOf("before_") == 0) {
            container.dir = "reverse";
            this.setAttribute("side", "bottom");
          }
          else {
            container.dir = "";
            this.setAttribute("side", "top");
          }
        }
        ]]>
        </body>
      </method>
    </implementation>
    <handlers>
      <handler event="popupshowing" phase="target">
      <![CDATA[
        var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow");
        arrow.hidden = this.anchorNode == null;
        document.getAnonymousElementByAttribute(this, "anonid", "arrowbox")
                .style.removeProperty("transform");

        this.adjustArrowPosition();

        if (this.getAttribute("animate") != "false") {
          this.setAttribute("animate", "open");
        }

        // set fading
        var fade = this.getAttribute("fade");
        var fadeDelay = 0;
        if (fade == "fast") {
          fadeDelay = 1;
        }
        else if (fade == "slow") {
          fadeDelay = 4000;
        }
        else {
          return;
        }

        this._fadeTimer = setTimeout(() => this.hidePopup(true), fadeDelay, this);
      ]]>
      </handler>
      <handler event="popuphiding" phase="target">
        let animate = (this.getAttribute("animate") != "false");

        if (this._fadeTimer) {
          clearTimeout(this._fadeTimer);
          if (animate) {
            this.setAttribute("animate", "fade");
          }
        }
        else if (animate) {
          this.setAttribute("animate", "cancel");
        }
      </handler>
      <handler event="popupshown" phase="target">
        this.setAttribute("panelopen", "true");
      </handler>
      <handler event="popuphidden" phase="target">
        this.removeAttribute("panelopen");
        if (this.getAttribute("animate") != "false") {
          this.removeAttribute("animate");
        }
      </handler>
      <handler event="popuppositioned" phase="target">
        this.adjustArrowPosition();
      </handler>
    </handlers>
  </binding>

  <binding id="tooltip" role="xul:tooltip"
           extends="chrome://global/content/bindings/popup.xml#popup-base">
    <content>
      <children>
        <xul:label class="tooltip-label" xbl:inherits="xbl:text=label" flex="1"/>
      </children>
    </content>

    <implementation>
      <field name="_mouseOutCount">0</field>
      <field name="_isMouseOver">false</field>

      <property name="label"
                onget="return this.getAttribute('label');"
                onset="this.setAttribute('label', val); return val;"/>

      <property name="page" onset="if (val) this.setAttribute('page', 'true');
                                   else this.removeAttribute('page');
                                   return val;"
                            onget="return this.getAttribute('page') == 'true';"/>
      <property name="textProvider"
                readonly="true">
        <getter>
        <![CDATA[
          if (!this._textProvider) {
            this._textProvider = Components.classes["@mozilla.org/embedcomp/default-tooltiptextprovider;1"]
                                 .getService(Components.interfaces.nsITooltipTextProvider);
          }
          return this._textProvider;
        ]]>
        </getter>
      </property>

      <!-- Given the supplied element within a page, set the tooltip's text to the text
           for that element. Returns true if text was assigned, and false if the no text
           is set, which normally would be used to cancel tooltip display.
        -->
      <method name="fillInPageTooltip">
        <parameter name="tipElement"/>
        <body>
        <![CDATA[
          let tttp = this.textProvider;
          let textObj = {}, dirObj = {};
          let shouldChangeText = tttp.getNodeText(tipElement, textObj, dirObj);
          if (shouldChangeText) {
            this.style.direction = dirObj.value;
            this.label = textObj.value;
          }
          return shouldChangeText;
        ]]>
        </body>
      </method>
    </implementation>

    <handlers>
      <handler event="mouseover"><![CDATA[
        var rel = event.relatedTarget;
        if (!rel)
          return;

        // find out if the node we entered from is one of our anonymous children
        while (rel) {
          if (rel == this)
            break;
          rel = rel.parentNode;
        }

        // if the exited node is not a descendant of ours, we are entering for the first time
        if (rel != this)
          this._isMouseOver = true;
      ]]></handler>

      <handler event="mouseout"><![CDATA[
        var rel = event.relatedTarget;

        // relatedTarget is null when the titletip is first shown: a mouseout event fires
        // because the mouse is exiting the main window and entering the titletip "window".
        // relatedTarget is also null when the mouse exits the main window completely,
        // so count how many times relatedTarget was null after titletip is first shown
        // and hide popup the 2nd time
        if (!rel) {
          ++this._mouseOutCount;
          if (this._mouseOutCount > 1)
            this.hidePopup();
          return;
        }

        // find out if the node we are entering is one of our anonymous children
        while (rel) {
          if (rel == this)
            break;
          rel = rel.parentNode;
        }

        // if the entered node is not a descendant of ours, hide the tooltip
        if (rel != this && this._isMouseOver) {
          this.hidePopup();
        }
      ]]></handler>

      <handler event="popupshowing"><![CDATA[
        if (this.page && !this.fillInPageTooltip(this.triggerNode)) {
          event.preventDefault();
        }
      ]]></handler>

      <handler event="popuphiding"><![CDATA[
        this._isMouseOver = false;
        this._mouseOutCount = 0;
      ]]></handler>
    </handlers>
  </binding>

  <binding id="popup-scrollbars" extends="chrome://global/content/bindings/popup.xml#popup">
    <content>
      <xul:hbox class="popup-internal-box" flex="1" orient="vertical" style="overflow: auto;">
        <children/>
      </xul:hbox>
    </content>
  </binding>

</bindings>
